package sk.hackcraft.util.simulation.concurrent;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import sk.hackcraft.util.simulation.Simulation;
import sk.hackcraft.util.simulation.SimulationController;
import sk.hackcraft.util.simulation.statistics.ReplicationReport;
import sk.hackcraft.util.simulation.statistics.SimulationReport;

public abstract class SimulationWorker implements SimulationController, UIController
{
	private static final int SIMULATION_REPORT_BUFFER_SIZE = 10000;
	private static final int REPLICATION_REPORT_BUFFER_SIZE = 10000;
	
	private static final int DELAY = 1000 / 30;
	
	private static final Logger logger = Logger.getLogger(SimulationWorker.class.getName());
	
	private Simulation simulation;
	private Thread simulationThread;
	private UserInterface userInterface;
	
	private boolean paused;
	private final Object pausedLock;
	
	private boolean stopped;
	private final Object stoppedLock;
	
	private boolean collecting;
	private final Object collectingLock;
	
	private int progress;
	private final Object progressLock;
	
	private int speed;
	private final Object speedLock;
	
	private final BlockingQueue<SimulationReport> simulationReports;
	private final BlockingQueue<ReplicationReport> replicationReports;
	
	private final Timer reportsTimer;
	
	private boolean systemEventExists;
	
	public SimulationWorker()
	{
		this.paused = false;
		this.pausedLock = new Object();
		
		this.stopped = false;
		this.stoppedLock = new Object();
		
		this.collecting = false;
		this.collectingLock = new Object();
		
		this.progress = 0;
		this.progressLock = new Object();
		
		this.simulationReports = new ArrayBlockingQueue<SimulationReport>(SIMULATION_REPORT_BUFFER_SIZE);
		this.replicationReports = new ArrayBlockingQueue<ReplicationReport>(REPLICATION_REPORT_BUFFER_SIZE);
		
		this.speed = 1;
		this.speedLock = new Object();
		
		this.reportsTimer = new Timer(DELAY, new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent e)
			{
				simulationReportAction();
				replicationReportAction();
			}
		});
	}
	
	private void simulationReportAction()
	{
		int size = simulationReports.size();
		
		for(; size>0; size--)
		{
			onReportSimulationResult(simulationReports.poll());
		}
	}
	
	private void replicationReportAction()
	{
		int size = replicationReports.size();
		
		for(; size>0; size--)
		{
			onReportReplicationResult(replicationReports.poll());
		}
	}
	
	@Override
	public final void setSimulation(Simulation simulation)
	{
		if(this.simulationThread == null || !this.simulationThread.isAlive())
		{
			this.simulation = simulation;
			this.simulation.setController(this);
		}
		else
		{
			throw new RuntimeException("Simulation is already running!");
		}
	}
	
	@Override
	public void setUserInterface(UserInterface userInterface)
	{
		this.userInterface = userInterface;
	}
	
	private void setRunningOnUI(boolean running)
	{
		if(this.userInterface != null)
		{
			this.userInterface.setRunningStatus(running);
		}
	}
	
	@Override
	public final void start()
	{
		if(this.simulation != null)
		{
			if(this.simulationThread == null || !this.simulationThread.isAlive())
			{
				setStopped(false);
				setPaused(false);
				
				simulationReports.clear();
				replicationReports.clear();
				
				reportsTimer.start();
				
				this.simulationThread = new Thread(new Runnable(){
					@Override
					public void run()
					{
						simulation.simulate();
					}
				});
				this.simulationThread.start();
				
				this.setRunningOnUI(true);
			}
			else
			{
				throw new RuntimeException("Simulation is already running!");
			}
		}
		else
		{
			throw new RuntimeException("No simulation avalaible!");
		}
	}
	
	@Override
	public final void togglePause()
	{
		synchronized(pausedLock)
		{
			this.paused = !this.paused;
		}
	}
	
	private void setPaused(boolean paused)
	{
		synchronized(pausedLock)
		{
			this.paused = paused;
		}
	}
	
	@Override
	public final boolean isPaused()
	{
		synchronized(pausedLock)
		{
			return paused;
		}
	}
	
	@Override
	public final void stop()
	{
		setStopped(true);
		waitForSimulationFinished();
		setRunningOnUI(false);
		reportsTimer.stop();
	}
	
	private void setStopped(boolean stopped)
	{
		synchronized(stoppedLock)
		{
			this.stopped = stopped;
		}
	}
	
	@Override
	public final boolean isStopped()
	{
		synchronized(stoppedLock)
		{
			return stopped;
		}
	}
	
	@Override
	public final void setCollecting(boolean collecting)
	{
		synchronized(collectingLock)
		{
			this.collecting = collecting;
		}
	}
	
	@Override
	public final boolean isCollecting()
	{
		synchronized(collectingLock)
		{
			return collecting;
		}
	}
	
	@Override
	public void finished()
	{
		setProgress(0);
		
		SwingUtilities.invokeLater(new Runnable(){
			@Override
			public void run()
			{
				setRunningOnUI(false);
				reportsTimer.stop();
				simulationReportAction();
				replicationReportAction();
			}
		});
	}
	
	@Override
	public void setProgress(int status)
	{
		synchronized(progressLock)
		{
			this.progress = status;
		}
		
		SwingUtilities.invokeLater(new Runnable(){
			@Override
			public void run()
			{
				 userInterface.setProgressState(getProgress());
			}
		});
	}
	
	private int getProgress()
	{
		synchronized(progressLock)
		{
			return progress;
		}
	}
	
	protected UserInterface getUserInterface()
	{
		return this.userInterface;
	}
	
	@Override
	public final void reportReplication(ReplicationReport replicationReport)
	{
		try
		{
			this.replicationReports.put(replicationReport);
		}
		catch (InterruptedException ex)
		{
			logger.log(Level.SEVERE, null, ex);
		}
	}
	
	protected abstract void onReportReplicationResult(ReplicationReport replicationReport);

	@Override
	public final void reportSimulation(SimulationReport simulationReport)
	{
		try
		{
			this.simulationReports.put(simulationReport);
		}
		catch (InterruptedException ex)
		{
			logger.log(Level.SEVERE, null, ex);
		}
	}
	
	protected abstract void onReportSimulationResult(SimulationReport simulationReport);
	
	@Override
	public boolean canPlanSystemEvent()
	{
		return (isCollecting() && !systemEventExists);
	}
	
	@Override
	public void setSystemEventExists(boolean exists)
	{
		systemEventExists = exists;
	}
	
	@Override
	public void setSimulationSpeed(int speed)
	{
		synchronized(speedLock)
		{
			this.speed = speed;
		}
	}
	
	@Override
	public int getSimulationSpeed()
	{
		synchronized(speedLock)
		{
			return speed;
		}
	}
	
	private void waitForSimulationFinished()
	{
		if(this.simulationThread != null && this.simulationThread.isAlive())
		{
			try
			{
				this.simulationThread.join();
			}
			catch (InterruptedException ex)
			{
				logger.log(Level.SEVERE, null, ex);
			}
		}
	}
}
